﻿/****************************************************************************************************
	Copyright (C) 2010 RapidWebDev Organization (http://rapidwebdev.org)
	Author: Eunge, Legal Name: Jian Liu, Email: eunge.liu@RapidWebDev.org

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ****************************************************************************************************/

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Globalization;
using System.Reflection;

namespace Publisher
{
	/// <summary>
	/// The class is used to publish RapidWebDev solution for release.
	/// </summary>
	internal class PublishManager
	{
		private string sourceSolutionDirectory;
		private string publishDestinationDirectory;

		internal string SubVersionDirectoryName { get; set; }

		internal PublishManager(string sourceSolutionDirectory, string publishDestinationDirectory)
		{
			this.sourceSolutionDirectory = sourceSolutionDirectory;
			this.publishDestinationDirectory = Path.Combine(publishDestinationDirectory, "Release");
			this.SubVersionDirectoryName = "_svn";
		}

		internal void Start()
		{
			this.InitializeReleaseDirectory();

			string sourceWebDirectory = Path.Combine(this.sourceSolutionDirectory, "Web");
			string publishWebDirectory = Path.Combine(this.publishDestinationDirectory, "Web");
			this.CopyDirectoryWithoutSubVersionDirectory(sourceWebDirectory, publishWebDirectory);

			this.UpdateWebCsproj();
			this.RemoveTemporaryFiles();
			this.CreateSolutionFile();
			this.RemoveWebCsprojKeyFile();
		}

		private void InitializeReleaseDirectory()
		{
			if (Directory.Exists(this.publishDestinationDirectory))
				Directory.Delete(this.publishDestinationDirectory, true);

			Directory.CreateDirectory(this.publishDestinationDirectory);
			Directory.CreateDirectory(Path.Combine(this.publishDestinationDirectory, "Library"));
		}

		private void CopyDirectoryWithoutSubVersionDirectory(string sourceDirectory, string destinationDirectory)
		{
			if (destinationDirectory[destinationDirectory.Length - 1] != Path.DirectorySeparatorChar)
				destinationDirectory += Path.DirectorySeparatorChar;

			if (!Directory.Exists(destinationDirectory)) 
				Directory.CreateDirectory(destinationDirectory);

			string[] fileSystemEntries = Directory.GetFileSystemEntries(sourceDirectory);
			foreach (string fileSystemEntry in fileSystemEntries)
			{
				string fileSystemEntryName = fileSystemEntry.Substring(fileSystemEntry.LastIndexOf(@"\") + 1);
				if (string.Equals(fileSystemEntryName, this.SubVersionDirectoryName, StringComparison.OrdinalIgnoreCase)) continue;

				// Sub directories
				if (Directory.Exists(fileSystemEntry))
					this.CopyDirectoryWithoutSubVersionDirectory(fileSystemEntry, Path.Combine(destinationDirectory, Path.GetFileName(fileSystemEntry)));
				// Files
				else
					File.Copy(fileSystemEntry, Path.Combine(destinationDirectory, Path.GetFileName(fileSystemEntry)), true);
			}

		}

		private void UpdateWebCsproj()
		{
			string webCsprojFileName = Path.Combine(this.publishDestinationDirectory, @"Web\Web.csproj");
			XDocument xdocWebCsproj = XDocument.Load(webCsprojFileName);
			XNamespace xmlNamespace = "http://schemas.microsoft.com/developer/msbuild/2003";

			IEnumerable<XElement> assemblyReferenceHitPaths = from c in xdocWebCsproj.Descendants()
							 where c.Name.LocalName == "Reference" && c.Elements(xmlNamespace + "HintPath").Count() > 0
							 select c.Element(xmlNamespace + "HintPath");
			try
			{
				foreach (XElement assemblyReferenceHitPath in assemblyReferenceHitPaths)
				{
					string target = ResolveRelativePath(assemblyReferenceHitPath.Value, this.sourceSolutionDirectory);
					string assemblyName = Path.GetFileName(assemblyReferenceHitPath.Value);
					File.Copy(target, this.publishDestinationDirectory + @"\Library\" + assemblyName, true);
					assemblyReferenceHitPath.Value = @"..\Library\" + assemblyName;
				}
			}
			catch (Exception e)
			{
				throw e;
			}

			IEnumerable<XElement> projectReferences = from element in xdocWebCsproj.Descendants()
									where element.Name.LocalName == "ProjectReference" && element.Attribute("Include") != null
									select element;

			XElement itemGroupElement = assemblyReferenceHitPaths.Count() > 0 ? assemblyReferenceHitPaths.FirstOrDefault().Parent.Parent : null;
			XElement parentProjectReference = projectReferences.Count() > 0 ? projectReferences.FirstOrDefault().Parent : null;

			IEnumerable<string> projectReferenceIncludes = from element in projectReferences select element.Attribute("Include").Value;
			try
			{
				foreach (string projectReferenceInclude in projectReferenceIncludes)
				{
					string referencedCsprojDirectory = Path.GetDirectoryName(projectReferenceInclude.Replace("..", this.sourceSolutionDirectory));
					string assemblyName = string.Format(CultureInfo.InvariantCulture, "BaoJianSoft.{0}dll", GetAssemblyName(projectReferenceInclude, "csproj"));

					//Copy the dll to Release
					if (File.Exists(referencedCsprojDirectory + @"\bin\Release\" + assemblyName))
						File.Copy(referencedCsprojDirectory + @"\bin\Release\" + assemblyName, this.publishDestinationDirectory + @"\Library\" + assemblyName, true);
					else
						throw new InvalidOperationException("Please compile web project in release mode before running this publish tool.");

					XElement newAssemblyReferenceElement = new XElement(xmlNamespace + "Reference", new XAttribute("Include", assemblyName),
												new XElement(xmlNamespace + "SpecificVersion", false),
												new XElement(xmlNamespace + "HintPath", @"..\Library\" + assemblyName));

					itemGroupElement.Add(newAssemblyReferenceElement);
				}

				parentProjectReference.Elements().Remove();
			}
			catch (Exception e)
			{
				throw e;
			}

			using (System.Xml.XmlWriter xw = System.Xml.XmlWriter.Create(webCsprojFileName))
			{
				xdocWebCsproj.Save(xw);
			}
		}

		private void RemoveTemporaryFiles()
		{
			List<string> fileSystemEntriesToDelete = new List<string>();
			string publishWebDirectory = Path.Combine(this.publishDestinationDirectory, "Web");

			fileSystemEntriesToDelete.Add(Path.Combine(publishWebDirectory, "Bin"));
			fileSystemEntriesToDelete.Add(Path.Combine(publishWebDirectory, "Obj"));
			fileSystemEntriesToDelete.Add(Path.Combine(publishWebDirectory, "log4net"));
			fileSystemEntriesToDelete.Add(Path.Combine(publishWebDirectory, "Web.csproj.user"));

			foreach (string fileSystemEntry in fileSystemEntriesToDelete)
			{
				if (Directory.Exists(fileSystemEntry))
					Directory.Delete(fileSystemEntry, true);

				if (File.Exists(fileSystemEntry))
					File.Delete(fileSystemEntry);
			}
		}

		private void CreateSolutionFile()
		{
			string manifestSolutionFile = "Publisher.RapidWebDev.sln";
			using (Stream solutionFileStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(manifestSolutionFile))
			using (StreamReader streamReader = new StreamReader(solutionFileStream))
			{
				string solutionFilePath = Path.Combine(this.publishDestinationDirectory, "RapidWebDev.sln");
				using (StreamWriter streamWriter = new StreamWriter(solutionFilePath, false, Encoding.UTF8, 1024))
				{
					string solutionFileBody = streamReader.ReadToEnd();
					streamWriter.Write(solutionFileBody);
				}
			}
		}

		private void RemoveWebCsprojKeyFile()
		{
			string assemblyInfoFilePath = Path.Combine(this.publishDestinationDirectory, @"Web\Properties\AssemblyInfo.cs");
			string assemblyInfoFileBody = null;
			using (StreamReader streamReader = new StreamReader(assemblyInfoFilePath, true))
			{
				assemblyInfoFileBody = streamReader.ReadToEnd();
			}

			assemblyInfoFileBody = assemblyInfoFileBody.Replace(@"[assembly: AssemblyKeyFile(@""..\..\..\..\RapidWebDev.public.snk"")]", "");
			assemblyInfoFileBody = assemblyInfoFileBody.Replace(@"[assembly: AssemblyDelaySign(true)]", "");

			using (StreamWriter streamWriter = new StreamWriter(assemblyInfoFilePath, false, Encoding.UTF8))
			{
				streamWriter.Write(assemblyInfoFileBody);
			}
		}

		private static string ResolveRelativePath(string relativedPath, string currentPath)
		{
			string[] levels = relativedPath.Split(new string[] { "\\" }, StringSplitOptions.None);
			string[] match = currentPath.Split(new string[] { "\\" }, StringSplitOptions.None);
			int count = 0;
			foreach (string level in levels)
			{
				if (level.Equals(".."))
					count++;
			}

			StringBuilder sb = new StringBuilder();
			for (int i = 0; i < match.Count() - count + 1; i++)
				sb.Append(match[i] + "\\");

			sb.Append(levels[levels.Length - 2] + "\\");
			sb.Append(levels[levels.Length - 1]);

			return sb.ToString();
		}

		private static string GetDirectory(string path)
		{
			if (System.IO.File.Exists(path))
			{
				return path.Substring(0, path.LastIndexOf("\\"));
			}
			else
				throw new FileNotFoundException("The specified file is not found.", path);
		}

		private static string GetAssemblyName(string path, string type)
		{
			return path.Substring(path.LastIndexOf("\\") + 1, path.LastIndexOf(type) - path.LastIndexOf("\\") - 1);
		}
	}
}
